﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Redmine.Laptop.API;
using Redmine.Laptop.API.Models;
using Redmine.Laptop.API.SearchCriteria;
using Redmine.Laptop.Common.Core;
using Redmine.Laptop.Common.Settings.SettingsUtilities;
using Redmine.Laptop.Reports.ReportParameters;

namespace Redmine.Laptop.Reports.Reports
{
    public abstract class Report : IDisposable
    {
        #region Member
        private readonly Parameters _parameters;
        protected readonly ExcelDocument Document = new ExcelDocument();

        /// <summary>
        /// Common project information
        /// </summary>
        protected class ProjectInfo
        {
            public string ProjectName;
            public int Index;
            public bool IsNonCommercial;
            public decimal Hours;
        }
        #endregion

        #region Properties
        protected RedmineService RedmineService { get; private set; }
        
        /// <summary>
        /// Developers name list
        /// </summary>
        private List<string> _developersName;
        protected IEnumerable<string> DevelopersNames
        {
            get { return _developersName ?? (_developersName = CommonSettings.ReportSettings.DevelopersNames.ToList()); }
        }

        /// <summary>
        /// Non commercial projects list
        /// </summary>
        private List<string> _nonCommercialProjectsNames;
        protected IEnumerable<string> NonCommercialProjectsNames
        {
            get { return _nonCommercialProjectsNames ?? (_nonCommercialProjectsNames = CommonSettings.ReportSettings.NonCommercialProjectNames.ToList()); }
        }

        /// <summary>
        /// Developers working hours per week
        /// </summary>
        private List<Tuple<string, int>> _developersWorkingHoursPerWeek;
        protected int GetWorkingHoursPerWeek(string developerName)
        {
            return (_developersWorkingHoursPerWeek ??
                    (_developersWorkingHoursPerWeek = CommonSettings.ReportSettings.DevelopersWorkingHoursPerWeek.ToList()))
                .Single(d => d.Item1 == developerName).Item2;
        }

        /// <summary>
        /// Projects information
        /// </summary>
        private List<ProjectInfo> _projectsInfo;
        protected List<ProjectInfo> ProjectsInfo
        {
            get { return _projectsInfo ?? (_projectsInfo = GetProjectInfo()); }
        }

        /// <summary>
        /// Time entries for selected date period
        /// </summary>
        private List<TimeEntry> _timeEntries;
        protected List<TimeEntry> TimeEntries
        {
            get { return _timeEntries ?? (_timeEntries = GetTimeEntries()); }
        }

        /// <summary>
        /// Report type
        /// </summary>
        protected ReportType Type { get; private set; }
        #endregion

        protected Report(RedmineService redmineService, ReportType type, Parameters parameters)
        {
            RedmineService = redmineService;
            Type = type;
            _parameters = parameters;
        }

        /// <summary>
        /// Get time entries for selected date interval
        /// </summary>
        /// <returns></returns>
        private List<TimeEntry> GetTimeEntries()
        {
            var timeEntryCriteria = new TimeEntryCriteria
            {
                FromDate = _parameters.FromDate,
                ThruDate = _parameters.ThruDate
            };

            // remove developers who does not include into xml file
            var timeEntries =
                RedmineService.GetTimeEntries(timeEntryCriteria)
                    .Where(te => DevelopersNames.Contains(te.User.UserName))
                    .ToList();

            // add developers who include into xml file, but they have not any time entries
            var lazyDevelopers = DevelopersNames.Except(timeEntries.Select(te => te.User.UserName).Distinct());
            var dummyEntries =
                from d in lazyDevelopers
                let names = d.Split(' ')
                let randomProject = timeEntries.FirstOrDefault()
                select new TimeEntry
                {
                    Project = new Project { ProjectName = randomProject != null ? randomProject.Project.ProjectName : "" },
                    User = new User { FirstName = names[0], LastName = names[1] }
                };

            timeEntries.AddRange(dummyEntries);

            return timeEntries;
        }
        
        /// <summary>
        /// Get project information from received time entries
        /// </summary>
        /// <returns></returns>
        private List<ProjectInfo> GetProjectInfo()
        {
            return TimeEntries.Select(te => te.Project.ProjectName)
                .Distinct()
                .OrderBy(p => p)
                .Select((p, index) => new ProjectInfo { ProjectName = p, Index = index, IsNonCommercial = NonCommercialProjectsNames.Contains(p), Hours = TimeEntries.Where(t => t.Project.ProjectName == p).Sum(t => t.Hours) })
                .ToList();
        }

        /// <summary>
        /// Create temp report file
        /// </summary>
        /// <returns></returns>
        protected FileStorage CreateReportFile()
        {
            var fileName = Path.GetTempFileName() + ".xlsx";
            var path = Path.Combine(Path.GetTempPath(), fileName);
            return Document.Save(path);
        }

        /// <summary>
        /// Build report
        /// </summary>
        /// <returns></returns>
        public FileStorage Build()
        {
            CreateReportStructure();
            return CreateReportFile();
        }

        public void Dispose()
        {
            Document.Dispose();
        }

        protected abstract void CreateReportStructure();
        
        /// <summary>
        /// Report name
        /// </summary>
        public abstract string ReportName { get; }

        
    }
}
